diff options
Diffstat (limited to 'app/[lng]')
| -rw-r--r-- | app/[lng]/admin/edp-progress/page.tsx | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/app/[lng]/admin/edp-progress/page.tsx b/app/[lng]/admin/edp-progress/page.tsx new file mode 100644 index 00000000..4efb739c --- /dev/null +++ b/app/[lng]/admin/edp-progress/page.tsx @@ -0,0 +1,431 @@ +"use client"; + +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Separator } from '@/components/ui/separator'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { + calculateVendorFormCompletion, + getProjectVendorCompletionSummary, + calculateVendorContractCompletion, + getVendorAllContractsCompletionSummary, + getAllVendorsContractsCompletionSummary, + getAllProjectsVendorCompletionSummary, + type VendorFormCompletionStats, + type ProjectVendorCompletionSummary, + type VendorAllContractsCompletionSummary +} from '@/lib/forms/vendor-completion-stats'; +import { Loader, TestTube, BarChart, FileText, TrendingUp } from 'lucide-react'; +import { toast } from 'sonner'; + +interface TestResult { + type: string; + data: VendorFormCompletionStats | ProjectVendorCompletionSummary | VendorAllContractsCompletionSummary | unknown; +} + +export default function EDPProgressTestPage() { + const [loading, setLoading] = React.useState<string | null>(null); + const [results, setResults] = React.useState<TestResult | null>(null); + + // Form inputs + const [contractItemId, setContractItemId] = React.useState('123'); + const [formCode, setFormCode] = React.useState('SPR_LST'); + const [projectId, setProjectId] = React.useState('1'); + const [vendorId, setVendorId] = React.useState('1'); + + const handleTest = async (testType: string, testFunction: () => Promise<unknown>) => { + setLoading(testType); + setResults(null); + + try { + const result = await testFunction(); + setResults({ type: testType, data: result }); + + if (result) { + toast.success(`${testType} 테스트 완료`); + } else { + toast.warning(`${testType} 결과가 없습니다`); + } + } catch (error) { + console.error(`Error in ${testType}:`, error); + toast.error(`${testType} 테스트 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`); + } finally { + setLoading(null); + } + }; + + const renderVendorFormStats = (stats: VendorFormCompletionStats) => ( + <div className="space-y-4"> + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold text-green-600">{stats.completionPercentage}%</div> + <p className="text-sm text-muted-foreground">완성도</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{stats.totalFilledFields}</div> + <p className="text-sm text-muted-foreground">입력된 필드</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{stats.totalRequiredFields}</div> + <p className="text-sm text-muted-foreground">총 필드</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{stats.tagCount}</div> + <p className="text-sm text-muted-foreground">태그 수</p> + </CardContent> + </Card> + </div> + + <Card> + <CardHeader> + <CardTitle className="text-lg">태그별 세부 현황</CardTitle> + </CardHeader> + <CardContent> + <ScrollArea className="h-48"> + <div className="space-y-2"> + {stats.detailsByTag.map((tag, index) => ( + <div key={index} className="flex items-center justify-between p-2 border rounded"> + <span className="font-medium">{tag.tagNo}</span> + <div className="flex items-center gap-2"> + <Badge variant={tag.completionPercentage >= 80 ? "default" : tag.completionPercentage >= 50 ? "secondary" : "destructive"}> + {tag.completionPercentage}% + </Badge> + <span className="text-sm text-muted-foreground"> + {tag.filledFields}/{tag.requiredFields} + </span> + </div> + </div> + ))} + </div> + </ScrollArea> + </CardContent> + </Card> + </div> + ); + + const renderProjectSummary = (summary: ProjectVendorCompletionSummary) => ( + <div className="space-y-4"> + <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold text-blue-600">{summary.averageCompletionPercentage}%</div> + <p className="text-sm text-muted-foreground">평균 완성도</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{summary.totalVendors}</div> + <p className="text-sm text-muted-foreground">참여 벤더</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-lg font-bold">{summary.projectCode}</div> + <p className="text-sm text-muted-foreground">프로젝트 코드</p> + </CardContent> + </Card> + </div> + + <Card> + <CardHeader> + <CardTitle className="text-lg">벤더별 완성도</CardTitle> + </CardHeader> + <CardContent> + <ScrollArea className="h-48"> + <div className="space-y-2"> + {summary.vendors.map((vendor, index) => ( + <div key={index} className="flex items-center justify-between p-2 border rounded"> + <span className="font-medium">{vendor.vendorName}</span> + <Badge variant={vendor.completionPercentage >= 80 ? "default" : vendor.completionPercentage >= 50 ? "secondary" : "destructive"}> + {vendor.completionPercentage}% + </Badge> + </div> + ))} + </div> + </ScrollArea> + </CardContent> + </Card> + </div> + ); + + const renderVendorAllContracts = (summary: VendorAllContractsCompletionSummary) => ( + <div className="space-y-4"> + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold text-purple-600">{summary.overallCompletionPercentage}%</div> + <p className="text-sm text-muted-foreground">전체 완성도</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{summary.totalContracts}</div> + <p className="text-sm text-muted-foreground">총 계약</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{summary.totalForms}</div> + <p className="text-sm text-muted-foreground">총 폼</p> + </CardContent> + </Card> + <Card> + <CardContent className="p-4"> + <div className="text-2xl font-bold">{summary.totalFilledFields}/{summary.totalRequiredFields}</div> + <p className="text-sm text-muted-foreground">입력 필드</p> + </CardContent> + </Card> + </div> + + <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> + <Card> + <CardHeader> + <CardTitle className="text-lg">프로젝트별 분석</CardTitle> + </CardHeader> + <CardContent> + <ScrollArea className="h-48"> + <div className="space-y-2"> + {summary.projectBreakdown.map((project, index) => ( + <div key={index} className="flex items-center justify-between p-2 border rounded"> + <div> + <div className="font-medium">{project.projectName}</div> + <div className="text-sm text-muted-foreground"> + 계약 {project.contractsCount}개, 폼 {project.formsCount}개 + </div> + </div> + <Badge variant={project.completionPercentage >= 80 ? "default" : "secondary"}> + {project.completionPercentage}% + </Badge> + </div> + ))} + </div> + </ScrollArea> + </CardContent> + </Card> + + <Card> + <CardHeader> + <CardTitle className="text-lg">계약별 세부 현황</CardTitle> + </CardHeader> + <CardContent> + <ScrollArea className="h-48"> + <div className="space-y-2"> + {summary.contracts.map((contract, index) => ( + <div key={index} className="flex items-center justify-between p-2 border rounded"> + <div> + <div className="font-medium">{contract.itemName}</div> + <div className="text-sm text-muted-foreground"> + {contract.projectName} - 폼 {contract.totalForms}개 + </div> + </div> + <Badge variant={contract.averageCompletionPercentage >= 80 ? "default" : "secondary"}> + {contract.averageCompletionPercentage}% + </Badge> + </div> + ))} + </div> + </ScrollArea> + </CardContent> + </Card> + </div> + </div> + ); + + return ( + <div className="container mx-auto p-6 space-y-6"> + <div className="flex items-center gap-2 mb-6"> + <TestTube className="h-6 w-6" /> + <h1 className="text-3xl font-bold">EDP Progress 서버 액션 테스트</h1> + </div> + + {/* Input Parameters */} + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <FileText className="h-5 w-5" /> + 테스트 파라미터 + </CardTitle> + <CardDescription> + 아래 값들을 수정하여 다양한 시나리오를 테스트할 수 있습니다. + </CardDescription> + </CardHeader> + <CardContent> + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + <div className="space-y-2"> + <Label htmlFor="contractItemId">Contract Item ID</Label> + <Input + id="contractItemId" + value={contractItemId} + onChange={(e) => setContractItemId(e.target.value)} + placeholder="123" + /> + </div> + <div className="space-y-2"> + <Label htmlFor="formCode">Form Code</Label> + <Input + id="formCode" + value={formCode} + onChange={(e) => setFormCode(e.target.value)} + placeholder="SPR_LST" + /> + </div> + <div className="space-y-2"> + <Label htmlFor="projectId">Project ID</Label> + <Input + id="projectId" + value={projectId} + onChange={(e) => setProjectId(e.target.value)} + placeholder="1" + /> + </div> + <div className="space-y-2"> + <Label htmlFor="vendorId">Vendor ID</Label> + <Input + id="vendorId" + value={vendorId} + onChange={(e) => setVendorId(e.target.value)} + placeholder="1" + /> + </div> + </div> + </CardContent> + </Card> + + {/* Test Buttons */} + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <BarChart className="h-5 w-5" /> + 테스트 액션들 + </CardTitle> + </CardHeader> + <CardContent> + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + <Button + onClick={() => handleTest('vendor-form', () => + calculateVendorFormCompletion(Number(contractItemId), formCode) + )} + disabled={loading !== null} + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'vendor-form' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">벤더 폼 완성도</div> + <div className="text-sm opacity-80">특정 벤더의 특정 폼 완성도</div> + </Button> + + <Button + onClick={() => handleTest('project-summary', () => + getProjectVendorCompletionSummary(Number(projectId), formCode) + )} + disabled={loading !== null} + variant="outline" + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'project-summary' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">프로젝트 요약</div> + <div className="text-sm opacity-80">프로젝트의 모든 벤더 완성도</div> + </Button> + + <Button + onClick={() => handleTest('vendor-contract', () => + calculateVendorContractCompletion(Number(vendorId), Number(contractItemId)) + )} + disabled={loading !== null} + variant="outline" + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'vendor-contract' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">벤더 계약 완성도</div> + <div className="text-sm opacity-80">특정 벤더의 특정 계약 완성도</div> + </Button> + + <Button + onClick={() => handleTest('vendor-all-contracts', () => + getVendorAllContractsCompletionSummary(Number(vendorId)) + )} + disabled={loading !== null} + variant="secondary" + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'vendor-all-contracts' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">벤더 전체 계약</div> + <div className="text-sm opacity-80">벤더의 모든 계약 완성도</div> + </Button> + + <Button + onClick={() => handleTest('all-vendors', () => + getAllVendorsContractsCompletionSummary() + )} + disabled={loading !== null} + variant="secondary" + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'all-vendors' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">전체 벤더 요약</div> + <div className="text-sm opacity-80">모든 벤더의 계약 완성도</div> + </Button> + + <Button + onClick={() => handleTest('all-projects', () => + getAllProjectsVendorCompletionSummary() + )} + disabled={loading !== null} + variant="secondary" + className="h-auto p-4 flex flex-col items-start space-y-2" + > + {loading === 'all-projects' && <Loader className="h-4 w-4 animate-spin" />} + <div className="font-semibold">전체 프로젝트 요약</div> + <div className="text-sm opacity-80">모든 프로젝트의 벤더 완성도</div> + </Button> + </div> + </CardContent> + </Card> + + <Separator /> + + {/* Results */} + {results && ( + <Card> + <CardHeader> + <CardTitle className="flex items-center gap-2"> + <TrendingUp className="h-5 w-5" /> + 테스트 결과: {results.type} + </CardTitle> + </CardHeader> + <CardContent> + {!results.data ? ( + <div className="text-center py-8 text-muted-foreground"> + 데이터가 없습니다. 파라미터를 확인해주세요. + </div> + ) : results.type === 'vendor-form' ? ( + renderVendorFormStats(results.data as VendorFormCompletionStats) + ) : results.type === 'project-summary' ? ( + renderProjectSummary(results.data as ProjectVendorCompletionSummary) + ) : results.type === 'vendor-all-contracts' ? ( + renderVendorAllContracts(results.data as VendorAllContractsCompletionSummary) + ) : ( + <div className="space-y-4"> + <div className="bg-muted p-4 rounded-lg"> + <pre className="text-sm overflow-auto max-h-96"> + {JSON.stringify(results.data, null, 2)} + </pre> + </div> + </div> + )} + </CardContent> + </Card> + )} + </div> + ); +} |
